"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
exports.__esModule = true;
var minimist_1 = require("minimist");
var http = require("http");
var https = require("https");
var fs = require("fs");
var url_1 = require("url");
var microtime = require("microtime");
var ws_1 = require("ws");
var packageJson = require("../package.json");
var suppressError = false;
function showToolVersion() {
    console.log("Version: " + packageJson.version);
}
// tslint:disable-next-line:cognitive-complexity
function executeCommandLine() {
    return __awaiter(this, void 0, void 0, function () {
        function showProgress() {
            if (responseCount % progressStep === 0) {
                console.log("Completed " + responseCount + " requests");
            }
        }
        function showResult() {
            var time = microtime.now() - startMoment;
            console.log("Concurrency Level:      " + concurrency);
            console.log("Time taken for tests:   " + (time / 1000000.0).toFixed(3) + " seconds");
            console.log("Complete requests:      " + (requests - errorCount));
            console.log("Failed requests:        " + errorCount);
            console.log("Requests per second:    " + (requests * 1000000.0 / time).toFixed(2) + " [#/sec] (mean)");
            console.log("Time per request:       " + (time * concurrency / requests / 1000.0).toFixed(3) + " [ms] (mean)");
            console.log("Time per request:       " + (time / requests / 1000.0).toFixed(3) + " [ms] (mean, across all concurrent requests)");
            console.log("Average time:           " + (totalRequestTimes.reduce(function (p, c) { return p + c; }, 0) / requests / 1000.0).toFixed(3) + " [ms]");
            totalRequestTimes.sort(function (a, b) { return a - b; });
            console.log("Min time:               " + (totalRequestTimes[0] / 1000.0).toFixed(3) + " [ms]");
            console.log("Max time:               " + (totalRequestTimes[totalRequestTimes.length - 1] / 1000.0).toFixed(3) + " [ms]");
            console.log("");
            console.log("Percentage of the requests served within a certain time (ms)");
            for (var _i = 0, _a = [50, 66, 75, 80, 90, 95, 98, 99, 100]; _i < _a.length; _i++) {
                var percent = _a[_i];
                var index = Math.floor(totalRequestTimes.length * percent / 100.0);
                if (index === totalRequestTimes.length) {
                    index = totalRequestTimes.length - 1;
                }
                var formattedPercent = percent === 100 ? '100' : " " + percent;
                console.log(" " + formattedPercent + "%      " + (totalRequestTimes[index] / 1000.0).toFixed(3));
            }
            // tslint:enable:no-console
        }
        var argv, showVersion, concurrency, requests, urls, url, method, timeout, filepath, file, contentType, headerObject, cookies, headers, _i, headers_1, header, tmp, tmp, keepAlive, minRequestCountPerClient, extraRequestCount, progressStep, responseCount, errorCount, totalRequestTimes, startMoment, urlObject_1, request_1, _loop_1, i, _loop_2, i;
        var _this = this;
        return __generator(this, function (_a) {
            argv = minimist_1["default"](process.argv.slice(2), { '--': true });
            showVersion = argv.v || argv.version;
            if (showVersion) {
                showToolVersion();
                return [2 /*return*/];
            }
            suppressError = argv.suppressError;
            concurrency = argv.c || 1;
            requests = argv.n || 1;
            urls = argv._;
            if (!urls) {
                throw new Error('Expect url');
            }
            if (urls.length !== 1) {
                throw new Error('Expect only one url');
            }
            url = urls[0];
            method = argv.m || 'GET';
            timeout = (argv.s || 30) * 1000;
            filepath = argv.f;
            file = filepath ? fs.readFileSync(filepath).toString() : '';
            contentType = argv.T || 'text/plain';
            headerObject = {};
            cookies = argv.C;
            if (cookies) {
                contentType.Cookie = Array.isArray(cookies) ? cookies.join(';') : cookies;
            }
            headers = argv.H;
            if (Array.isArray(headers)) {
                for (_i = 0, headers_1 = headers; _i < headers_1.length; _i++) {
                    header = headers_1[_i];
                    tmp = header.split(':');
                    if (tmp.length >= 2) {
                        headerObject[tmp[0].trim()] = tmp[1].trim();
                    }
                }
            }
            else if (typeof headers === 'string') {
                tmp = headers.split(':');
                if (tmp.length >= 2) {
                    headerObject[tmp[0].trim()] = tmp[1].trim();
                }
            }
            keepAlive = argv.k;
            if (keepAlive) {
                headerObject.Connection = 'keep-alive';
            }
            minRequestCountPerClient = Math.floor(requests / concurrency);
            extraRequestCount = requests % concurrency;
            progressStep = Math.floor(requests / 10.0);
            responseCount = 0;
            errorCount = 0;
            totalRequestTimes = [];
            startMoment = microtime.now();
            if (url.startsWith('http://') || url.startsWith('https://')) {
                urlObject_1 = new url_1.URL(url);
                request_1 = urlObject_1.protocol === 'https:' ? https.request : http.request;
                _loop_1 = function (i) {
                    var agent = urlObject_1.protocol === 'https:' ? new https.Agent() : new http.Agent();
                    var requestCount = i < extraRequestCount ? minRequestCountPerClient + 1 : minRequestCountPerClient;
                    (function () { return __awaiter(_this, void 0, void 0, function () {
                        var j;
                        return __generator(this, function (_a) {
                            switch (_a.label) {
                                case 0:
                                    j = 0;
                                    _a.label = 1;
                                case 1:
                                    if (!(j < requestCount)) return [3 /*break*/, 4];
                                    return [4 /*yield*/, new Promise(function (resolve, reject) {
                                            var requestStartMoment = microtime.now();
                                            var req = request_1({
                                                protocol: urlObject_1.protocol,
                                                host: urlObject_1.host,
                                                hostname: urlObject_1.hostname,
                                                port: +urlObject_1.port,
                                                method: method,
                                                path: urlObject_1.pathname + urlObject_1.search + (urlObject_1.hash ? '#' + urlObject_1.hash : ''),
                                                agent: agent,
                                                timeout: timeout,
                                                headers: __assign({ 'Content-Type': contentType, 'Content-Length': file.length }, headerObject)
                                            }, function (res) {
                                                responseCount++;
                                                if (res.statusCode >= 500) {
                                                    errorCount++;
                                                }
                                                totalRequestTimes.push(microtime.now() - requestStartMoment);
                                                resolve();
                                            });
                                            req.on('error', function (err) {
                                                if (err) {
                                                    // do nothing
                                                }
                                                responseCount++;
                                                errorCount++;
                                                totalRequestTimes.push(microtime.now() - requestStartMoment);
                                                resolve();
                                            });
                                            if (file) {
                                                req.write(file);
                                            }
                                            req.end();
                                        })];
                                case 2:
                                    _a.sent();
                                    showProgress();
                                    if (responseCount === requests) {
                                        showResult();
                                    }
                                    _a.label = 3;
                                case 3:
                                    j++;
                                    return [3 /*break*/, 1];
                                case 4: return [2 /*return*/];
                            }
                        });
                    }); })();
                };
                for (i = 0; i < concurrency; i++) {
                    _loop_1(i);
                }
            }
            else if (url.startsWith('ws://') || url.startsWith('wss://')) {
                _loop_2 = function (i) {
                    var requestCount = i < extraRequestCount ? minRequestCountPerClient + 1 : minRequestCountPerClient;
                    var ws = new ws_1["default"](url, {
                        headers: headerObject
                    });
                    ws.onopen = function () {
                        (function () { return __awaiter(_this, void 0, void 0, function () {
                            var j;
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        j = 0;
                                        _a.label = 1;
                                    case 1:
                                        if (!(j < requestCount)) return [3 /*break*/, 4];
                                        return [4 /*yield*/, new Promise(function (resolve, reject) {
                                                var requestStartMoment = microtime.now();
                                                ws.send(file || 'Hello world!', function (error) {
                                                    if (error) {
                                                        errorCount++;
                                                    }
                                                    responseCount++;
                                                    totalRequestTimes.push(microtime.now() - requestStartMoment);
                                                    resolve();
                                                });
                                            })];
                                    case 2:
                                        _a.sent();
                                        showProgress();
                                        if (responseCount === requests) {
                                            showResult();
                                        }
                                        _a.label = 3;
                                    case 3:
                                        j++;
                                        return [3 /*break*/, 1];
                                    case 4:
                                        ws.close();
                                        return [2 /*return*/];
                                }
                            });
                        }); })();
                    };
                };
                for (i = 0; i < concurrency; i++) {
                    _loop_2(i);
                }
            }
            else {
                throw new Error('Invalid url');
            }
            return [2 /*return*/];
        });
    });
}
executeCommandLine().then(function () {
    console.log('ws-benchmark success.');
}, function (error) {
    if (error instanceof Error) {
        console.log(error.message);
    }
    else {
        console.log(error);
    }
    if (!suppressError) {
        process.exit(1);
    }
});
